﻿#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)

#include <stdio.h>

/* 1. 结构体*/

//1.1基础知识
//内置类型：int char long float double....
//自定义类型中有一种就叫结构体类型，便于复杂对象的描述，结构的每个成员可以是不同类型的变量。

//1.2结构体的声明
//struct tag			//struct是结构体关键字，tag是结构体标签
//{
//	member - list;
//}variable - list;		//variable - list可省略

//声明的结构体类型struct peo，成员可以是标量(char,int...)、数组、指针，甚至是其他结构体
//struct peo
//{
//	char name[20];//名字		20是怎么定的？就是向内存中申请20个字符的空间，存储字符串
//	int age;//年龄
//	char sex[5];//性别			一个汉字占两个字符
//	char tele[12];//电话		电话若是11位，再加个\0，所以给12
//}p1, p2;		//p1, p2是利用结构体类型struct peo创建的2个变量，放在此处就是2个全局变量，但不建议这样做

//1.3特殊的声明
//struct     //匿名结构体类型-只能用一次
//{
//	int a;
//	char b;
//	float c;
//}x;
//
//struct
//{
//	int a;
//	char b;
//	float c;
//}a[20], * p;		//*和前面的匿名结构体类型struct{int a;char b;float c;}组合起来叫结构体指针类型，p就是一个结构体指针变量
//
//int main()
//{
//	p = &x;		//两边的类型不同，会报警告
//
//	return 0;
//}

//1.4 结构体的自引用
//数据结构：数据在内存中的存储结构；有线形数据结构（顺序表、链表）、树形数据结构（二叉树）
//设计链表的一个节点-错误的写法
//struct Node		//定义一个节点时，这个节点里面既可以放一个数值，又可以放下一个节点
//{
//	int data;
//	struct Node next;
//};				//可行否？如果可以，那节点的大小是多少-sizeof(struct Node)是多少？-错误
//
//struct Node		//正确写法：第一个节点包含数值和下一个节点的地址
//{				//一个节点包含两个部分：数据域（存放数值）和指针域（存放地址）   
//	int data;
//	struct Node* next;		//结构体里面可包含一个同类型的结构体指针
//};
//
//typedef struct  //这样写代码，可行否？-对匿名结构体类型重命名，错误的
//{
//	int data;
//	Node* next;
//}Node;
//
//typedef struct Node
//{
//	int data;
//	struct Node* next;
//}Node;

// 1.5 结构体变量的定义和初始化
//struct peo					//struct peo{char name[20]; char tele[12]; char sex[5];};就是结构体类型，利用类型创建变量时结构体类型可简写为struct peo
//							//name、tele、sex、high就是结构体成员！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！！
//{
//	char name[20];//名字		20是怎么定的？就是向内存中申请20个字符的空间，存储字符串
//	char tele[12];//电话		电话若是11位，再加个\0，所以给12
//	char sex[5];//性别			一个汉字占两个字符
//	int high;//身高
//};
//
//struct st
//{
//	struct peo p;		//结构体的成员可以是结构体
//	int num;
//	float f;
//};
//
//void print1(struct peo p)	//形参p是实参p1的一份临时拷贝
//{
//	printf("%s %s %s %d\n", p.name, p.tele, p.sex, p.high);
//}
//
//void print2(const struct peo* sp)	//sp是指向一个结构体的指针,结构体指针类型是struct peo*
//{
//	printf("%s %s %s %d\n", sp->name, sp->tele, sp->sex, sp->high);
//}
//
//int main()
//{ 
//	struct peo p1 = { "张三","12345678912","男",181 };		//结构体局部变量p1的创建并初始化
//	struct st s = { {"lisi","12345678921","男",181},100,3.14f }; //3.14后不加f默认是double类型，但注意浮点数在内存中不能精确保存
//
//	printf("%s %s %s %d\n", p1.name, p1.tele, p1.sex, p1.high);	 //结构体成员的访问：结构体变量(对象).成员名
//	printf("%s %s %s %d %d %f\n", s.p.name, s.p.tele, s.p.sex, s.p.high, s.num, s.f);
//
//	print1(p1);		//传值
//	print2(&p1);	//传址
//
//	return 0;
//}
//结构体变量访问成员：结构体变量(对象).成员名
//结构体指针访问指向变量的成员：结构体指针变量->成员名

/* 1.8 结构体传参*/
//上面的 print1 和 print2 函数哪个好些？
//答案是：首选print2函数。
//原因：函数传参的时候，参数是需要压栈的。
//如果传递一个结构体对象的时候，结构体过大，参数压栈的的系统开销比较大，所以会导致性能的下降。

//结构体传参的时候，要传结构体的地址。


/************************************************   进阶内容   自定义类型：结构体，枚举，联合体    *************************************************/

/* 1.6结构体内存对齐—计算结构体类型所占内存空间的大小。*/
#include <stddef.h>--offsetof

//struct S1
//{
// char c1;		//c1在0偏移处开始存储1个字节
// int i;			//i的对齐数是4；i在4偏移处开始存储4个字节
// char c2;		//c2的对齐数是1；c2在8偏移处开始存储1个字节
//};
//
//struct S2
//{
//	char c1;	//c1在0偏移处开始存储1个字节
//	char c2;	//c2的对齐数是1；所以c2在1偏移处开始存储1个字节
//	int i;		//i的对齐数是4；所以i在4偏移处开始存储4个字节
//};
//
//struct S3
//{
//	double d;	//d在0偏移处开始存储8个字节
//	char c;		//c的对齐数是1；所以c在8偏移处开始存储1个字节
//	int i;		//i的对齐数是4；所以i在12偏移处开始存储4个字节
//};
//
//struct S4
//{
//	char c1;
//	struct S3 s3;	//s3在8偏移处开始存储16个字节
//	double d;		//d的对齐数是8；所以d在24偏移处开始存储8个字节
//};
//
//int main()
//{ 
//	struct S1 s1;
//	struct S2 s2;		//创建变量就会在内存中开辟空间
//	struct S3 s3;
//	struct S4 s4;
//
//	printf("%d\n", sizeof(struct S1));		//12
//	printf("%d\n", sizeof(struct S2));		//8
//	printf("%d\n", sizeof(struct S3));		//16
//	printf("%d\n", sizeof(struct S4));		//32
//	printf("\n");
//	printf("%d\n", offsetof(struct S1, c1));	//宏offsetof可以返回结构体成员在这个类型中创建的变量中的偏移量  0
//	printf("%d\n", offsetof(struct S1, i));		//4
//	printf("%d\n", offsetof(struct S1, c2));	//8
//	printf("\n");
//	printf("%d\n", offsetof(struct S2, c1));	//宏offsetof可以返回结构体成员在这个类型中创建的变量中的偏移量  0
//	printf("%d\n", offsetof(struct S2, c2));	//1
//	printf("%d\n", offsetof(struct S2, i));		//4
//
//	return 0;
//}

//首先得掌握结构体的对齐规则：
//1. 第一个成员在与结构体变量偏移量为0的地址处。
//2. 其他成员变量要对齐到偏移量是对齐数整数倍的地址处。
//对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
//VS中默认的值为8；其他编译器没有默认对齐数，对齐数就是成员自身的大小
//3. 结构体总大小(单位Byte)为最大对齐数（每个成员变量都有一个对齐数）的整数倍。
//4. 如果嵌套了结构体的情况，嵌套的结构体对齐到自己的最大对齐数的整数倍处，结构体的整体大小就是所有最大对齐数（含嵌套结构体的对齐数）的整数倍。

//为什么存在内存对齐 ?
//1. 平台原因(移植原因)：
//不是所有的硬件平台都能访问任意地址上的任意数据的；某些硬件平台只能在某些地址处取某些特定类型的数据，否则抛出硬件异常。
//2. 性能原因：
//数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
//原因在于，为了访问未对齐的内存，处理器需要作两次内存访问；而对齐的内存访问仅需要一次访问。
//
//总体来说：结构体的内存对齐是拿空间来换取时间的做法。
// 
//那在设计结构体的时候，我们既要满足对齐，又要节省空间，如何做到：让占用空间小的成员尽量集中在一起。

/*1.7 修改默认对齐数— #pragma 这个预处理指令可以改变默认对齐数。*/

//#pragma pack(8)	//设置默认对齐数为8
//struct S1
//{
//	char c1;
//	int i;
//	char c2;
//};
//#pragma pack()	//取消设置的默认对齐数，还原为默认
//#pragma pack(1)	//设置默认对齐数为1—就相当于连续存放，不存在对齐了
//struct S2
//{
//	char c1;
//	int i;
//	char c2;
//};
//#pragma pack()	//取消设置的默认对齐数，还原为默认
//int main()
//{
//	//输出的结果是什么？
//	printf("%d\n", sizeof(struct S1));		//12
//	printf("%d\n", sizeof(struct S2));		//6
//	return 0;
//}


/* 2.位段-利用结构体实现，可以节省空间 */
//2.1位段的声明和结构是类似的，但有两个不同：
//1.位段的成员必须是 int、unsigned int 或signed int 。（也可以是char类型）
//2.位段的成员名后边有一个冒号和一个数字。

//struct A			//A就是一个位段类型。
//{
//	//4Byte-32bit
//	int _a : 2;		//:2的意思是变量_a只占2个bit位
//	int _b : 5;
//	int _c : 10;
//	//最开始开辟4个字节的空间还剩15个没用，但又有30个bit要存，所以又开辟4个字节的空间
//	int _d : 30;	//一共47个bit位，6个字节（共48个bit）就可存储，但结果是8
//};
//
//int main()
//{
//	printf("%d\n", sizeof(struct A));		//8
//
//	return 0;
//}

//2.2位段的内存分配
//1. 位段的成员可以是 int; unsigned int; signed int 或者是 char （属于整形家族）类型
//2. 位段的空间上是按照需要以4个字节（ int ）或者1个字节（ char ）的方式来开辟的。
//3. 位段涉及很多不确定因素，位段是不跨平台的，注重可移植的程序应该避免使用位段。

//一个例子
//struct S
//{
//	char a : 3;		//a只占3个bit，那就开辟1个字节(8bit)的空间
//	char b : 4;
//	char c : 5;
//	char d : 4;
//};
//
//int main()
//{
//	struct S s = { 0 };
//	printf("%d\n", sizeof(struct S));
//	s.a = 10;		//将10存进3个bit位中，但10的二进制序列是1010，是4个bit位，放不下，那就截断，只放010
//	s.b = 12;
//	s.c = 3;
//	s.d = 4;
//
//	return 0;
//}
//空间是如何开辟的？-看b站

// 2.3位段的跨平台问题
//1. int 位段被当成有符号数还是无符号数是不确定的。
//2. 位段中最大位的数目不能确定。（16位机器最大16，32位机器最大32，写成27，在16位机器会出问题。
//3. 位段中的成员在内存中从左向右分配，还是从右向左分配标准尚未定义。
//4. 当一个结构包含两个位段，第二个位段成员比较大，无法容纳于第一个位段剩余的位时，是舍弃剩余的位还是利用，这是不确定的。


/* 3.枚举-把可能的取值一一列举 */

//3.1 枚举类型的定义
//enum Day//星期
//{
//	Mon,
//	Tues,
//	Wed,
//	Thur,
//	Fri,
//	Sat,
//	Sun
//};
//enum Sex//性别
//{
//	MALE,
//	FEMALE,
//	SECRET
//};
//enum Color//颜色
//{
//	RED,
//	GREEN,
//	BLUE
//};
////以上定义的 enum Day ， enum Sex ， enum Color 都是枚举类型。
////{ }中的内容是枚举类型的可能取值，也叫枚举常量。这些可能取值都是有值的，默认从0开始，依次递增1，当然在声明枚举类型的时候也可以赋初值。
//
//int main()
//{
//	enum Day d = Fri;
//	printf("%d\n", Mon);	//0
//	printf("%d\n", Tues);	//1
//	printf("%d\n", Wed);	//2
//
//	return 0;
//}

//3.2 枚举的优点
//我们可以使用 #define 定义常量，为什么非要使用枚举？
//1. 增加代码的可读性和可维护性
//2. 和#define定义的标识符比较，枚举有类型检查，更加严谨。在C++中可以体现出来
//3. 便于调试
//4. 使用方便，一次可以定义多个常量

//3.3 枚举的使用
//enum Color//颜色
//{
//	RED = 1,
//	GREEN = 2,
//	BLUE = 4
//};
//
//int main()
//{
//	enum Color clr = GREEN;	//只能拿枚举常量给枚举变量赋值，才不会出现类型的差异。
//	//clr = 5;              	//ok??
//
//	return 0;
//}


/* 4联合（共用体）*/
//4.1 联合类型的定义
//联合也是一种特殊的自定义类型.这种类型定义的变量也包含一系列的成员，特征是这些成员共用同一块空间（所以联合也叫共用体）。

//联合类型的声明
//union Un
//{
//	char c;
//	int i;
//};
//
//int main()
//{
//	//联合变量的定义
//	union Un un;
//	//计算联合变量的大小											
//	printf("%d\n", sizeof(un));		//4
//	
//	printf("%p\n", &un);
//	printf("%p\n", &un.c);
//	printf("%p\n", &un.i);
//
//	return 0;
//}

// 4.2联合的特点
//联合的成员是共用同一块内存空间的，这样一个联合变量的大小，至少是最大成员的大小（因为联合至少得有能力保存最大的那个成员）。

//面试题：判断当前计算机的大小端存储
//int check_sys()
//{
//	int a = 1;
//	return	*(char*)&a;		//先取出4个字节的起始地址，再强转为char*，char*的指针解引用就可拿到一个字节里的元素
//}
//int check_sys()		//利用联合体
//{
//	union Un
//	{
//		char c;
//		int i;
//	}u;				//联合变量u在内存中占4个字节
//	u.i = 1;		//先在这4个字节中存储十进制数1
//	return u.c;
//}
//
//int main()
//{
//	int a = 1;		//十进制数1用十六进制表示就是0x 00 00 00 01
//	//若用小端存储：地址是从左到右由低到高变化，那么在内存中倒着存，就是01 00 00 00
//	//若用大端存储：在内存中存的就是00 00 00 01
//	//取出第一个字节地址下的内容，若是01，计算机就是小端存储；若是00，计算机就是大端存储
//	int ret = check_sys();
//	if (ret == 1)
//	{
//		printf("小端");
//	}
//	else
//		printf("大端");
//
//	return 0;
//}

// 4.3 联合大小的计算
//联合的大小至少是最大成员的大小。
//当最大成员大小不是最大对齐数的整数倍的时候，就要对齐到最大对齐数的整数倍。
//union Un1
//{
//	char c[5];	//数组c的类型是char [5]
//	int i;		//最大成员的大小是5Byte，不是最大对齐数4的整数倍，所以联合的大小是8，后面3个字节的空间就浪费掉
//};
//union Un2
//{
//	short c[7];
//	int i;
//};
////下面输出的结果是什么？
//
//int main()
//{
//	printf("%d\n", sizeof(union Un1));		//8
//	printf("%d\n", sizeof(union Un2));		//16
//	printf("%d\n", sizeof(short));			//2
//
//	return 0;
//}
